﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

namespace Jemgine.Renderer
{
    public class Renderer
    {
        private GraphicsDevice GraphicsDevice;
		Effect DrawSpriteEffect;
		VertexPositionTexture[] VertexBuffer = new VertexPositionTexture[8];
		Vector2[] TempVectors = new Vector2[8];

		EffectParameter _viewMatrix;
		EffectParameter _projectionMatrix;
		EffectParameter _worldMatrix;
		EffectParameter _color;
		EffectParameter _texture;
        Jemgine.Engine.Renderer.Camera Cam;

        public Matrix View { get { return Cam.View; } }
        public Matrix Projection { get { return Cam.Projection; } }

        public Renderer(GraphicsDevice GraphicsDevice, ContentManager Content)
        {
            reset(GraphicsDevice, Content);
        }

        public void reset(GraphicsDevice GraphicsDevice, ContentManager Content)
        {
            this.GraphicsDevice = GraphicsDevice;

			DrawSpriteEffect = Content.Load<Effect>("Engine.Content/DrawSprite");

			_viewMatrix = DrawSpriteEffect.Parameters["View"];
			_projectionMatrix = DrawSpriteEffect.Parameters["Projection"];
			_worldMatrix = DrawSpriteEffect.Parameters["World"];
			_color = DrawSpriteEffect.Parameters["Color"];
			_texture = DrawSpriteEffect.Parameters["Texture"];
        }

		bool DrawingScene = false;

		public void BeginScene(Jemgine.Engine.Renderer.Camera Camera)
		{
			if (DrawingScene) throw new InvalidProgramException("Must call EndScene before calling BeginScene again.");
			//GraphicsDevice.VertexDeclaration = VertexDeclaration;

            Cam = Camera;
			_viewMatrix.SetValue(Camera.View);
			_projectionMatrix.SetValue(Camera.Projection);
			DrawingScene = true;
		}

		public void EndScene()
		{
			if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling EndScene.");
			DrawingScene = false;
		}

        void AddVertex(int Pos, Vector2 V, Matrix TextureMatrix)
        {
            VertexBuffer[Pos].Position = new Vector3(V, 0);
            VertexBuffer[Pos].TextureCoordinate = Vector2.Transform(V, TextureMatrix);
        }

        public void TexturedRuntimePolygon(Matrix Transform, 
            Data.RuntimePolygon Poly, 
            Texture2D Texture,
            Data.TextureAlignment Alignment)
        {
            int StartV = 0;
            int EndV = Poly.Count - 1;
            int BufferPos = 0;
            Matrix M = Matrix.CreateRotationZ(MathHelper.ToRadians(Alignment.Angle))
                * Matrix.CreateScale(-Alignment.Scale.X, Alignment.Scale.Y, 1.0f)
                * Matrix.CreateTranslation(Alignment.Offset.X, Alignment.Offset.Y, 0);

            while (StartV <= EndV)
            {
                AddVertex(BufferPos, Poly[StartV], M); ++BufferPos;
                if (StartV != EndV) { AddVertex(BufferPos, Poly[EndV], M); ++BufferPos; }
                ++StartV;
                --EndV;
            }

            _texture.SetValue(Texture);
            _color.SetValue(Color.White.ToVector4());
            _worldMatrix.SetValue(Transform);

            DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
            DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, VertexBuffer, 0, Poly.Count - 2);

        }

        public void TexturedQuad(Matrix Transform,
            Data.Quad Poly,
            Texture2D Texture)
        {
            VertexBuffer[0].Position = new Vector3(Poly[0].Position, 0.0f);
            VertexBuffer[1].Position = new Vector3(Poly[1].Position, 0.0f); 
            VertexBuffer[3].Position = new Vector3(Poly[2].Position, 0.0f); 
            VertexBuffer[2].Position = new Vector3(Poly[3].Position, 0.0f);
            VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
            VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
            VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
            VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);

            _texture.SetValue(Texture);
            _color.SetValue(Color.White.ToVector4());
            _worldMatrix.SetValue(Transform);

            DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
            DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, VertexBuffer, 0, Poly.Count - 2);

        }

        public void RawDraw(Texture2D Texture, Vector2 v0, Vector2 v1, Vector2 v2, Vector2 v3, Vector4 Color)
		{
			if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling RawDraw.");

			VertexBuffer[0].Position = new Vector3(v0, 0);
			VertexBuffer[1].Position = new Vector3(v1, 0);
			VertexBuffer[3].Position = new Vector3(v2, 0);
			VertexBuffer[2].Position = new Vector3(v3, 0);
			VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
			VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
			VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
			VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);

			_texture.SetValue(Texture);
			_color.SetValue(Color);
			_worldMatrix.SetValue(Matrix.Identity);

			DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
			DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

			GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
				(PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);

		}

        public void DrawFullScreenQuad(Texture2D Texture, Vector2 TextureScale)
        {
            if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling DrawFullScreenQuad.");

            VertexBuffer[0].Position = new Vector3(Cam.UnProject(new Vector2(0, 0)), 0);
            VertexBuffer[1].Position = new Vector3(Cam.UnProject(new Vector2(Cam.viewportDimensions.X, 0)), 0);
            VertexBuffer[3].Position = new Vector3(Cam.UnProject(new Vector2(Cam.viewportDimensions.X, Cam.viewportDimensions.Y)), 0);
            VertexBuffer[2].Position = new Vector3(Cam.UnProject(new Vector2(0, Cam.viewportDimensions.Y)), 0);

            VertexBuffer[0].TextureCoordinate = new Vector2(VertexBuffer[0].Position.X, VertexBuffer[0].Position.Y) * TextureScale;
            VertexBuffer[1].TextureCoordinate = new Vector2(VertexBuffer[1].Position.X, VertexBuffer[1].Position.Y) * TextureScale;
            VertexBuffer[2].TextureCoordinate = new Vector2(VertexBuffer[2].Position.X, VertexBuffer[2].Position.Y) * TextureScale;
            VertexBuffer[3].TextureCoordinate = new Vector2(VertexBuffer[3].Position.X, VertexBuffer[3].Position.Y) * TextureScale;

            _texture.SetValue(Texture);
            _color.SetValue(Vector4.One);
            _worldMatrix.SetValue(Matrix.Identity);

            DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
            DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);

        }

        public void RawFullScreenQuad()
        {
            VertexBuffer[0].Position = new Vector3(-1, -1, 0);
            VertexBuffer[1].Position = new Vector3(-1, 1, 0);
            VertexBuffer[3].Position = new Vector3(1, 1, 0);
            VertexBuffer[2].Position = new Vector3(1, -1, 0);

            VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
            VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
            VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
            VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);
        }

        public void DrawOrientedSprite(
            Matrix Transform,
            Texture2D Texture,
            Vector4 Color,
            Vector2 Position,
            Vector2 Orientation)
        {
            if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling DrawSprite.");

            VertexBuffer[2].Position = new Vector3(Orientation.X - Orientation.Y, Orientation.X + Orientation.Y, 0.0f);
            VertexBuffer[3].Position = new Vector3(-(Orientation.X + Orientation.Y), Orientation.X - Orientation.Y, 0.0f);
            VertexBuffer[1].Position = new Vector3(Orientation.Y - Orientation.X, -(Orientation.X + Orientation.Y), 0.0f);
            VertexBuffer[0].Position = new Vector3(Orientation.X + Orientation.Y, Orientation.Y - Orientation.X, 0.0f);


                VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
                VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
                VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
                VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);
           

            _texture.SetValue(Texture);
            _color.SetValue(Color);
            _worldMatrix.SetValue(
                Matrix.CreateTranslation(Position.X, Position.Y, 0) *
                Transform
                );

            DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
            DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);

        }

		public void DrawSprite(
            Matrix Transform, 
            Texture2D Texture, 
            Vector4 Color, 
            Vector2 Position, 
            Vector2 Scale, 
            float Angle, 
            bool Flip,
            bool Additive)
		{
			if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling DrawSprite.");

			VertexBuffer[0].Position = new Vector3(-0.5f, -0.5f, 0);
			VertexBuffer[1].Position = new Vector3(-0.5f, 0.5f, 0);
			VertexBuffer[3].Position = new Vector3(0.5f, 0.5f, 0);
			VertexBuffer[2].Position = new Vector3(0.5f, -0.5f, 0);

			if (!Flip)
			{
				VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
				VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
				VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
				VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);
			}
			else
			{
				VertexBuffer[0].TextureCoordinate = new Vector2(1, 1);
				VertexBuffer[1].TextureCoordinate = new Vector2(1, 0);
				VertexBuffer[3].TextureCoordinate = new Vector2(0, 0);
				VertexBuffer[2].TextureCoordinate = new Vector2(0, 1);
			}

			_texture.SetValue(Texture);
			_color.SetValue(Color);
			_worldMatrix.SetValue(
				Matrix.CreateScale(Scale.X, Scale.Y, 1.0f) *
				Matrix.CreateRotationZ(Angle) * 
				Matrix.CreateTranslation(Position.X, Position.Y, 0) *
				Transform
				);

			DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[Additive ? 1 : 0];
			DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

			GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
				(PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);

		}

        public void RawDrawSprite()
        {
            VertexBuffer[0].Position = new Vector3(-0.5f, -0.5f, 0);
            VertexBuffer[1].Position = new Vector3(-0.5f, 0.5f, 0);
            VertexBuffer[3].Position = new Vector3(0.5f, 0.5f, 0);
            VertexBuffer[2].Position = new Vector3(0.5f, -0.5f, 0);

                VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
                VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
                VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
                VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);

        }

		public void DrawSpriteOutline(Matrix Transform, Texture2D Texture, Vector4 Color, Vector2 Position, Vector2 Scale, float Angle, float Width)
		{
			if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling DrawSprite.");

			TempVectors[0] = new Vector2(-0.5f, -0.5f);
			TempVectors[1] = new Vector2(-0.5f, 0.5f);
			TempVectors[2] = new Vector2(0.5f, 0.5f);
			TempVectors[3] = new Vector2(0.5f, -0.5f);

			Transform =
				Matrix.CreateScale(Scale.X, Scale.Y, 1.0f) *
				Matrix.CreateRotationZ(Angle) *
				Matrix.CreateTranslation(Position.X, Position.Y, 0) *
				Transform;

			DrawLine(Transform, Texture, TempVectors[0], TempVectors[1], Color, Width);
			DrawLine(Transform, Texture, TempVectors[1], TempVectors[2], Color, Width);
			DrawLine(Transform, Texture, TempVectors[2], TempVectors[3], Color, Width);
			DrawLine(Transform, Texture, TempVectors[3], TempVectors[0], Color, Width);
		}

		public void DrawLine(Matrix Transform, Texture2D Texture, Vector2 Start, Vector2 End, Vector4 Color, float Width)
		{
			if (!DrawingScene) throw new InvalidProgramException("Must call BeginScene before calling DrawLine.");

			Start = Vector2.Transform(Start, Transform);
			End = Vector2.Transform(End, Transform);


			Vector2 LineNormal = Jemgine.Math.Vector.EdgeNormal(Start, End);
			LineNormal *= Width * 0.5f;

			VertexBuffer[0].Position = new Vector3(Start + LineNormal, 0);
			VertexBuffer[1].Position = new Vector3(Start - LineNormal, 0);
			VertexBuffer[3].Position = new Vector3(End - LineNormal, 0);
			VertexBuffer[2].Position = new Vector3(End + LineNormal, 0);
			VertexBuffer[0].TextureCoordinate = new Vector2(0, 1);
			VertexBuffer[1].TextureCoordinate = new Vector2(0, 0);
			VertexBuffer[3].TextureCoordinate = new Vector2(1, 0);
			VertexBuffer[2].TextureCoordinate = new Vector2(1, 1);

			_texture.SetValue(Texture);
			_color.SetValue(Color);
			_worldMatrix.SetValue(Matrix.Identity);

			DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
			DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

			GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
				(PrimitiveType.TriangleStrip, VertexBuffer, 0, 2);



		}

        public void DrawElementLine(Matrix Transform,
           Texture2D Texture,
           Vector4 Color,
           VertexPositionTexture[] Verticies)
        {
            _texture.SetValue(Texture);
            _color.SetValue(Color);
            _worldMatrix.SetValue(Transform);

            DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
            DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, Verticies, 0, 2);
        }

        public void DrawSpline(Matrix Transform, 
            Texture2D Texture, 
            Vector4 Color,
            VertexPositionTexture[] Verticies, 
            int Segments)
        {
            _texture.SetValue(Texture);
            _color.SetValue(Color);
            _worldMatrix.SetValue(Transform);

            DrawSpriteEffect.CurrentTechnique = DrawSpriteEffect.Techniques[0];
            DrawSpriteEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>
                (PrimitiveType.TriangleStrip, Verticies, 0, Segments * 2);
        }

        public void ForceEndAll()
        {
            this.DrawingScene = false;
        }
    }
}
